#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plot
import time
from sklearn import preprocessing
'''
比较3中不同梯度下降方法
1、批量梯度下降
2、随机梯度下降
3、小批量梯度下降
'''

def run():
    # 读取数据
    path = 'E:\培训教程\python\唐宇迪-机器学习课程\机器学习算法配套案例实战\梯度下降求解逻辑回归\梯度下降\data\LogiReg_data.txt'
    pddate = pd.read_table(path,sep=',',header=None,names=['Exam 1','Exam 2','Admitted'])
    '''在数据集的第0列插入一列，列名为ones，值全为1，插入的列用来和偏置项参数相乘'''
    pddate.insert(0,'ones',1)
    '''# 将df数据结构转换成numpy，成为矩阵'''
    orig_data = pddate.as_matrix()
    # orig_data = preprocessing.scale(orig_data)
    # min_max_scaler = preprocessing.MinMaxScaler()
    # orig_data = min_max_scaler.fit_transform(orig_data)

    X,y = shuffle(orig_data)
    '''构造3个参数,为3行一列的矩阵，可以和X的矩阵相乘'''
    theta = np.zeros([3,1])

    theta, iter, costs, grad, dur = descent(orig_data, theta, 8, STOP_GRAD, thresh=0.05, alpha=0.000001)

    fig, ax = plot.subplots(figsize=(12, 4))
    ax.plot(np.arange(len(costs)), costs, 'r')
    ax.set_xlabel('Iterations')   # 迭代次数
    ax.set_ylabel('Cost')         # 损失值

    plot.show()


def plt(pddate):
    '''数据可视化'''
    fix, ax = plot.subplots(figsize=(10, 5))
    positive = pddate[pddate['Admitted'] == 1]  # 被录取的人
    negative = pddate[pddate['Admitted'] == 0]  # 没有被录取的人
    ax.scatter(positive['Exam 1'], positive['Exam 2'], s=30, c='b', marker='o', label='Admitted')  # 被录取的人的分数散点图
    ax.scatter(negative['Exam 1'], negative['Exam 2'], s=30, c='r', marker='x', label='Not Admitted')
    ax.legend()  # 作用是显示图形上角的标签，也就是scatter中的label
    ax.set_xlabel('Exam 1 Score')
    ax.set_xlabel('Exam 2 Score')
    plot.show()

'''定义sigmod函数，将z映射到概率上,转换为逻辑回归问题'''
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

'''对矩阵做乘法'''
def model(X,theta):
    return sigmoid(np.dot(X,theta))

'''损失函数'''
def cost(X,y,theta):
    left = np.multiply(-y, np.log(model(X, theta)))
    right = np.multiply(1 - y, np.log(1 - model(X, theta)))
    return np.sum(left - right) / (len(X))

'''计算梯度，对函数求偏导数，有三个参数，计算三次偏导数'''
def gradient(X, y, theta):
    grad = np.zeros(theta.shape)
    '''
    # ravel() 函数将多维数组拉平成为1为数组,长度为100
    # error 就是每一项的误差，就是公式中真实值和预测值的差
    '''
    error = (model(X, theta) - y).ravel()
    '''因为有三个参数，所有需要对函数求三次偏导，len(theta.ravel()) 意思为将theta从3行1列，拉平成1行1列，并取长度为3'''
    for j in range(len(theta.ravel())):
        term = np.multiply(error, X[:, j])
        grad[j,0] = np.sum(term) / len(X)
    return grad



STOP_ITER = 0
STOP_COST = 1
STOP_GRAD = 2
'''梯度下降的停止策率'''
def stopCriterion(type, value, threshold):
    if type == STOP_ITER:        return value >= threshold
    elif type == STOP_COST:      return abs(value[-1] - value[-2]) < threshold
    elif type == STOP_GRAD:      return np.linalg.norm(value) < threshold

'''对数据进行打乱洗牌'''
def shuffle(data):
    np.random.shuffle(data)
    cols = data.shape[1]
    '''取自变量，为矩阵的前三列'''
    X = data[:,0:cols-1]
    '''取因变量为矩阵的最后一列，用前面三列的值预测最后一列'''
    y = data[:,cols-1:cols]
    return X,y

'''
#梯度下降求解
# thresh 迭代次数 ; alpha 学习率
'''
def descent(data, theta, batchSize, stopType, thresh, alpha):
    init_time = time.time()
    i = 0  # 迭代次数
    k = 0  # batch
    X, y = shuffle(data)
    grad = np.zeros(theta.shape)  # 计算的梯度,是一种停止策率
    costs = [cost(X, y, theta)]   # 损失值,是一种停止策率
    while True:
        grad = gradient(X[k:k + batchSize], y[k:k + batchSize], theta)
        k += batchSize  # 取batch数量个数据
        if k >= 16:
            k = 0
            X, y = shuffle(data)
        theta = theta - alpha * grad  # 学习率
        costs.append(cost(X, y, theta))  # 计算新的损失
        i += 1
        if stopType == STOP_ITER:       value = i
        elif stopType == STOP_COST:     value = costs
        elif stopType == STOP_GRAD:     value = grad
        if stopCriterion(stopType, value, thresh): break
    return theta, i - 1, costs, grad, time.time() - init_time


# 计算数据
def runExpe(data, theta, batchSize, stopType, thresh, alpha):
    theta, iter, costs, grad, dur = descent(data, theta, batchSize, stopType, thresh, alpha)
    return theta, iter, costs, grad, dur


if __name__ == '__main__':
    run()